17.5 清理
与复杂的标记过程不同,清理操作要简单得多。此时,所有未被标记的白色对象都不再被引用,可简单地将其内存回收。
mgc.go
func gcSweep(mode int) { // 设置work.spans=h_allspans // 还记得FixAlloc里面的recordspan么? gcCopySpans()
// 更新代龄 mheap_.sweepgen+=2 mheap_.sweepdone=0 sweep.spanidx=0
// 阻塞模式 if!_ConcurrentSweep||mode==gcForceBlockMode{ for sweepone() != ^uintptr(0) { sweep.npausesweep++ }
return
}
// 并发模式 if sweep.parked{ sweep.parked=false ready(sweep.g,0) } }
并发清理同样由一个专门的goroutine完成,它在runtime.main调用gcenable时被创建。
mgc.go
func gcenable() { c:=make(chan int,1) go bgsweep(c) ←c memstats.enablegc=true //now that runtime is initialized,GC is okay }
并发清理本质上就是一个死循环,被唤醒后开始执行清理任务。通过遍历所有span对象,触发内存分配器的回收操作。任务完成后,再次休眠,等待下次任务。
mgcsweep.go
var sweep sweepdata
// 并发清理状态 type sweepdata struct{ g *g parked bool }
func bgsweep(c chan int) { // 当前goroutine sweep.g=getg() sweep.parked=true
// 让gcenable退出 c←1
// 休眠,等待gcSweep唤醒 goparkunlock(&sweep.lock, “GC sweep wait”,traceEvGoBlock,1)
for{ // 循环清理所有span for gosweepone() != ^uintptr(0) { // 并发调度,避免长时间占用CPU Gosched() }
if!gosweepdone() {
continue
}
// 清理结束,休眠直到再次被唤醒
sweep.parked=true
goparkunlock(&sweep.lock, "GC sweep wait",traceEvGoBlock,1)
} }
func sweepone()uintptr{ g :=getg() sg:=mheap_.sweepgen
for{ // 从0开始的work.spans(h_allspans) 索引号 idx:=xadd(&sweep.spanidx,1) -1
// 全部完成
if idx>=uint32(len(work.spans)) {
mheap_.sweepdone=1
return^uintptr(0)
}
s:=work.spans[idx]
// 跳过闲置的span,直接更新代龄
if s.state!=mSpanInUse{
s.sweepgen=sg
continue
}
// 跳过已经或者正在被清理的span
if s.sweepgen!=sg-2|| !cas(&s.sweepgen,sg-2,sg-1) {
continue
}
// 调用内存分配器回收方法
npages:=s.npages
if!mSpan_Sweep(s,false) {
npages=0
}
return npages
} }
内存回收操作mSpan_Sweep,请参考第16章的相关章节。